﻿
#include "ffmpegdl.h"

namespace spadas
{
    typedef void (*GetFFMPEGInterfaceVersion)(UInt& major, UInt& minor);
    typedef Interface<IFFMPEGInterfaceV100> (*GetFFMPEGInterfaceV100)();
    typedef Interface<IFFMPEGInterfaceV101> (*GetFFMPEGInterfaceV101)();
    typedef Interface<IFFMPEGInterfaceV102> (*GetFFMPEGInterfaceV102)();
    typedef Interface<IFFMPEGInterfaceV103> (*GetFFMPEGInterfaceV103)();
    typedef Interface<IFFMPEGInterfaceV104> (*GetFFMPEGInterfaceV104)();
    typedef Interface<IFFMPEGInterfaceV105> (*GetFFMPEGInterfaceV105)();

    Bool ffmpegInitialized = FALSE;
    UInt ffmpegInterfVerMajor = 0, ffmpegInterfVerMinor = 0;
    UInt ffmpegLibVersion = 0;
    Interface<IFFMPEGInterfaceV100> ffmpegV100;
    Interface<IFFMPEGInterfaceV101> ffmpegV101;
    Interface<IFFMPEGInterfaceV102> ffmpegV102;
    Interface<IFFMPEGInterfaceV103> ffmpegV103;
    Interface<IFFMPEGInterfaceV104> ffmpegV104;
    Interface<IFFMPEGInterfaceV105> ffmpegV105;

    void init_ffmpeg_interface()
    {
        if (ffmpegInitialized) return;

#if SPADAS_VERSION_MAJOR >= 9
        Path rootPath = Path::entryFolderPath();
#else
        Path rootPath = Path::executableFolderPath();
#endif

        auto vers = Array<Int>::create(4, 6, 4, 3, 2);

        Bool ok = FALSE;
        for (auto e = vers.firstElem(); e.valid(); ++e)
        {
            Int ver = e.value();
            String fileName = (String)"ffmpegdl_" + ver;

            LibraryLoader loader;
            if (!loader.openWithName(rootPath, fileName)) continue;

            GetFFMPEGInterfaceVersion getFFMPEGInterfaceVersion = (GetFFMPEGInterfaceVersion)loader.getSymbol("_Z28get_ffmpeg_interface_versionRjS_");
            if (getFFMPEGInterfaceVersion == NULL) continue;

            UInt major = 0, minor = 0;
            getFFMPEGInterfaceVersion(major, minor);
            if (major != FFMPEGDL_VER_MAJOR || minor < FFMPEGDL_VER_MINOR) continue;

            if (major == 1 && minor >= 0)
            {
                GetFFMPEGInterfaceV100 getFFMPEGDLInterface = (GetFFMPEGInterfaceV100)loader.getSymbol("_Z25get_ffmpeg_interface_v100v");
                if (getFFMPEGDLInterface == NULL) break;
                ffmpegV100 = getFFMPEGDLInterface();
                if (ffmpegV100.isNull()) break;
            }
            if (major == 1 && minor >= 1)
            {
                GetFFMPEGInterfaceV101 getFFMPEGDLInterface = (GetFFMPEGInterfaceV101)loader.getSymbol("_Z25get_ffmpeg_interface_v101v");
                if (getFFMPEGDLInterface == NULL) break;
                ffmpegV101 = getFFMPEGDLInterface();
                if (ffmpegV101.isNull()) break;
            }
            if (major == 1 && minor >= 2)
            {
                GetFFMPEGInterfaceV102 getFFMPEGDLInterface = (GetFFMPEGInterfaceV102)loader.getSymbol("_Z25get_ffmpeg_interface_v102v");
                if (getFFMPEGDLInterface == NULL) break;
                ffmpegV102 = getFFMPEGDLInterface();
                if (ffmpegV102.isNull()) break;
            }
            if (major == 1 && minor >= 3)
            {
                GetFFMPEGInterfaceV103 getFFMPEGDLInterface = (GetFFMPEGInterfaceV103)loader.getSymbol("_Z25get_ffmpeg_interface_v103v");
                if (getFFMPEGDLInterface == NULL) break;
                ffmpegV103 = getFFMPEGDLInterface();
                if (ffmpegV103.isNull()) break;
            }
            if (major == 1 && minor >= 4)
            {
                GetFFMPEGInterfaceV104 getFFMPEGDLInterface = (GetFFMPEGInterfaceV104)loader.getSymbol("_Z25get_ffmpeg_interface_v104v");
                if (getFFMPEGDLInterface == NULL) break;
                ffmpegV104 = getFFMPEGDLInterface();
                if (ffmpegV104.isNull()) break;
            }
            if (major == 1 && minor >= 5)
            {
                GetFFMPEGInterfaceV105 getFFMPEGDLInterface = (GetFFMPEGInterfaceV105)loader.getSymbol("_Z25get_ffmpeg_interface_v105v");
                if (getFFMPEGDLInterface == NULL) break;
                ffmpegV105 = getFFMPEGDLInterface();
                if (ffmpegV105.isNull()) break;
            }

            ffmpegInterfVerMajor = major;
            ffmpegInterfVerMinor = minor;
            ffmpegLibVersion = (UInt)ver;
            ok = TRUE;
            break;
        }

        ffmpegInitialized = TRUE;
        if (!ok) console::print("[FFMPEGDL] No available FFMPEG libraries detected.");
    }

    void getFFMPEGInterfaceVersion(UInt& major, UInt& minor)
    {
        init_ffmpeg_interface();
        major = ffmpegInterfVerMajor;
        minor = ffmpegInterfVerMinor;
    }

    UInt getFFMPEGLibraryVersion()
    {
        init_ffmpeg_interface();
        return ffmpegLibVersion;
    }
}

using namespace spadas;

SPADAS_DEFAULT_API String getUsingFFMPEGDLVersion()
{
    return (String) FFMPEGDL_VER_MAJOR + "." + FFMPEGDL_VER_MINOR;
}

//////////////////////////////////////////////////////////////////////////////////////

Int spadas::av_get_error_code_value(AVErrorCode code)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_get_error_code_value(code);
    else return 0;
}

void spadas::av_frame_free(Pointer framePtr)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_frame_free(framePtr);
}

Pointer spadas::av_frame_alloc()
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_frame_alloc();
    else return NULL;
}

void spadas::av_frame_set_format(Pointer frame, AVPixelFormat format)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_frame_set_format(frame, format);
}

void spadas::av_frame_set_width(Pointer frame, int width)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_frame_set_width(frame, width);
}

void spadas::av_frame_set_height(Pointer frame, int height)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_frame_set_height(frame, height);
}

Int spadas::av_frame_get_buffer(Pointer frame, Int align)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_frame_get_buffer(frame, align);
    else return -1;
}

AVPixelFormat spadas::av_frame_get_format(Pointer frame)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_frame_get_format(frame);
    else return AVPixelFormat::AV_PIX_FMT_NONE;
}

Int spadas::av_frame_get_width(Pointer frame)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_frame_get_width(frame);
    else return 0;
}

Int spadas::av_frame_get_height(Pointer frame)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_frame_get_height(frame);
    else return 0;
}

Int* spadas::av_frame_get_linesize(Pointer frame)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_frame_get_linesize(frame);
    else return NULL;
}

Byte** spadas::av_frame_get_data(Pointer frame)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_frame_get_data(frame);
    else return NULL;
}

Pointer spadas::avcodec_find_encoder_by_name(const Char *name)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_find_encoder_by_name(name);
    else return NULL;
}

Pointer spadas::avcodec_alloc_context3(Pointer codec)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_alloc_context3(codec);
    else return NULL;
}

void spadas::avcodec_set_bit_rate(Pointer ctx, Long bitRate)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_set_bit_rate(ctx, bitRate);
}

void spadas::avcodec_set_width(Pointer ctx, Int width)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_set_width(ctx, width);
}

void spadas::avcodec_set_height(Pointer ctx, Int height)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_set_height(ctx, height);
}

void spadas::avcodec_set_time_base(Pointer ctx, Int num, Int den)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_set_time_base(ctx, num, den);
}

void spadas::avcodec_set_framerate(Pointer ctx, Int num, Int den)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_set_framerate(ctx, num, den);
}

void spadas::avcodec_set_pix_fmt(Pointer ctx, AVPixelFormat format)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_set_pix_fmt(ctx, format);
}

void spadas::avcodec_set_qmin(Pointer ctx, Int qmin)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_set_qmin(ctx, qmin);
}

void spadas::avcodec_set_qmax(Pointer ctx, Int qmax)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_set_qmax(ctx, qmax);
}

Int spadas::avcodec_open2(Pointer ctx, Pointer codec, Pointer optionsPtr)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_open2(ctx, codec, optionsPtr);
    else return -1;
}

AVPixelFormat spadas::avcodec_get_pix_fmt(Pointer ctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_get_pix_fmt(ctx);
    else return AVPixelFormat::AV_PIX_FMT_NONE;
}

Int spadas::avcodec_get_width(Pointer ctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_get_width(ctx);
    else return 0;
}

Int spadas::avcodec_get_height(Pointer ctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_get_height(ctx);
    else return 0;
}

Pointer spadas::sws_getContext(Int srcW, Int srcH, AVPixelFormat srcFormat, Int dstW, Int dstH, AVPixelFormat dstFormat, Int flags, Pointer srcFilter, Pointer dstFilter, Pointer param)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->sws_getContext(srcW, srcH, srcFormat, dstW, dstH, dstFormat, flags, srcFilter, dstFilter, param);
    else return NULL;
}

Pointer spadas::av_packet_alloc()
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_packet_alloc();
    else return NULL;
}

void spadas::avcodec_free_context(Pointer ctxPtr)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avcodec_free_context(ctxPtr);
}

void spadas::av_packet_free(Pointer packetPtr)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_packet_free(packetPtr);
}

void spadas::sws_freeContext(Pointer ctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->sws_freeContext(ctx);
}

Int spadas::sws_scale(Pointer ctx, Byte **srcSlice, Int *srcStride, Int srcSliceY, Int srcSliceH, Byte **dst, Int *dstStride)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->sws_scale(ctx, srcSlice, srcStride, srcSliceY, srcSliceH, dst, dstStride);
    else return 0;
}

Int spadas::avcodec_send_frame(Pointer ctx, Pointer frame)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_send_frame(ctx, frame);
    else return -1;
}

Int spadas::avcodec_receive_packet(Pointer ctx, Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_receive_packet(ctx, packet);
    else return -1;
}

Int spadas::av_packet_get_size(Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_packet_get_size(packet);
    else return 0;
}

Byte* spadas::av_packet_get_data(Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_packet_get_data(packet);
    else return NULL;
}

void spadas::av_packet_unref(Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_packet_unref(packet);
}

void spadas::av_log_set_level(Int level)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_log_set_level(level);
}

Pointer spadas::avcodec_find_decoder_by_name(const Char *name)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_find_decoder_by_name(name);
    else return NULL;
}

Int spadas::av_new_packet(Pointer packet, Int size)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_new_packet(packet, size);
    else return -1;
}

void spadas::av_packet_set_pts(Pointer packet, Long pts)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_packet_set_pts(packet, pts);
}

void spadas::av_packet_set_dts(Pointer packet, Long dts)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_packet_set_dts(packet, dts);
}

Int spadas::avcodec_send_packet(Pointer ctx, Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_send_packet(ctx, packet);
    else return -1;
}

Int spadas::avcodec_receive_frame(Pointer ctx, Pointer frame)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_receive_frame(ctx, frame);
    else return -1;
}

Pointer spadas::avcodec_get_hw_config(Pointer codec, Int index)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_get_hw_config(codec, index);
    else return NULL;
}

Int spadas::avcodec_hw_config_get_methods(Pointer hwConfig)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_hw_config_get_methods(hwConfig);
    else return 0;
}

AVHWDeviceType spadas::avcodec_hw_config_get_device_type(Pointer hwConfig)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_hw_config_get_device_type(hwConfig);
    else return AVHWDeviceType::AV_HWDEVICE_TYPE_NONE;
}

Int spadas::av_hwdevice_ctx_create(Pointer hwctxPtr, AVHWDeviceType type, const Char *device, Pointer options, Int flags)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_hwdevice_ctx_create(hwctxPtr, type, device, options, flags);
    else return -1;
}

Pointer spadas::av_buffer_ref(Pointer buf)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_buffer_ref(buf);
    else return NULL;
}

void spadas::av_buffer_unref(Pointer bufPtr)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_buffer_unref(bufPtr);
}

void spadas::avcodec_set_hw_device_ctx(Pointer ctx, Pointer hwctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avcodec_set_hw_device_ctx(ctx, hwctx);
}

Int spadas::av_opt_set(Pointer obj, const Char *name, const Char *val, Int search_flags)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_opt_set(obj, name, val, search_flags);
    else return -1;
}

void spadas::avcodec_set_gop_size(Pointer ctx, Int gopSize)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avcodec_set_gop_size(ctx, gopSize);
}

void spadas::avcodec_set_max_b_frames(Pointer ctx, Int maxBFrames)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avcodec_set_max_b_frames(ctx, maxBFrames);
}

Pointer spadas::avcodec_get_priv_data(Pointer ctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avcodec_get_priv_data(ctx);
    else return NULL;
}

void spadas::av_frame_set_pts(Pointer frame, Long pts)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_frame_set_pts(frame, pts);
}

Int spadas::avformat_open_input(Pointer fmtctxPtr, const Char *url, Pointer fmt, Pointer optionsPtr)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_open_input(fmtctxPtr, url, fmt, optionsPtr);
    else return -1;
}

Int spadas::avformat_find_stream_info(Pointer fmtctx, Pointer optionsPtr)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_find_stream_info(fmtctx, optionsPtr);
    else return -1;
}

void spadas::avformat_free_context(Pointer fmtctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_free_context(fmtctx);
}

UInt spadas::avformat_get_nb_streams(Pointer fmtctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_get_nb_streams(fmtctx);
    else return 0;
}

Pointer *spadas::avformat_get_streams(Pointer fmtctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_get_streams(fmtctx);
    else return NULL;
}

AVMediaType spadas::avformat_stream_get_codec_type(Pointer stream)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_stream_get_codec_type(stream);
    else return AVMediaType::AVMEDIA_TYPE_NONE;
}

AVCodecID spadas::avformat_stream_get_codec_id(Pointer stream)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_stream_get_codec_id(stream);
    else return AVCodecID::AV_CODEC_ID_NONE;
}

Int spadas::avformat_stream_get_width(Pointer stream)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_stream_get_width(stream);
    else return 0;
}

Int spadas::avformat_stream_get_height(Pointer stream)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_stream_get_height(stream);
    else return 0;
}

void spadas::avformat_stream_get_time_base(Pointer stream, Int& num, Int& den)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avformat_stream_get_time_base(stream, num, den);
}

void spadas::avformat_close_input(Pointer fmtctxPtr)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avformat_close_input(fmtctxPtr);
}

Int spadas::av_seek_frame(Pointer fmtctx, Int stream_index, Long timestamp, Int flags)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_seek_frame(fmtctx, stream_index, timestamp, flags);
    else return -1;
}

Int spadas::av_read_frame(Pointer fmtctx, Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_read_frame(fmtctx, packet);
    else return -1;
}

Int spadas::av_packet_get_stream_index(Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_packet_get_stream_index(packet);
    else return -1;
}

Long spadas::av_packet_get_dts(Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_packet_get_dts(packet);
    else return -1;
}

Int spadas::avformat_alloc_output_context2(Pointer fmtctxPtr, Pointer oformat, const Char *format_name, const Char *filename)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_alloc_output_context2(fmtctxPtr, oformat, format_name, filename);
    else return -1;
}

Pointer spadas::avformat_new_stream(Pointer fmtctx, Pointer c)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_new_stream(fmtctx, c);
    else return NULL;
}

void spadas::avformat_stream_set_codec_type(Pointer stream, AVMediaType type)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avformat_stream_set_codec_type(stream, type);
}

void spadas::avformat_stream_set_codec_id(Pointer stream, AVCodecID id)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avformat_stream_set_codec_id(stream, id);
}

void spadas::avformat_stream_set_bit_rate(Pointer stream, Long rate)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avformat_stream_set_bit_rate(stream, rate);
}

void spadas::avformat_stream_set_width(Pointer stream, Int width)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avformat_stream_set_width(stream, width);
}

void spadas::avformat_stream_set_height(Pointer stream, Int height)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avformat_stream_set_height(stream, height);
}

void spadas::avformat_stream_set_time_base(Pointer stream, Int num, Int den)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avformat_stream_set_time_base(stream, num, den);
}

void spadas::avformat_stream_set_codec_tag(Pointer stream, UInt tag)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->avformat_stream_set_codec_tag(stream, tag);
}

Pointer spadas::avformat_get_pb_ptr(Pointer fmtctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_get_pb_ptr(fmtctx);
    else return NULL;
}

Int spadas::avio_open(Pointer ioctxPtr, const Char *url, Int flags)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avio_open(ioctxPtr, url, flags);
    else return -1;
}

Int spadas::avformat_write_header(Pointer fmtctx, Pointer optionsPtr)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_write_header(fmtctx, optionsPtr);
    else return -1;
}

Pointer spadas::avformat_get_pb(Pointer fmtctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avformat_get_pb(fmtctx);
    else return NULL;
}

Int spadas::avio_close(Pointer ioctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->avio_close(ioctx);
    else return -1;
}

void spadas::av_init_packet(Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_init_packet(packet);
}

void spadas::av_packet_set_size(Pointer packet, Int size)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_packet_set_size(packet, size);
}

void spadas::av_packet_set_data(Pointer packet, Byte *data)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_packet_set_data(packet, data);
}

void spadas::av_packet_set_flags(Pointer packet, Int flags)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) ffmpegV100->av_packet_set_flags(packet, flags);
}

Int spadas::av_packet_get_flags(Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_packet_get_flags(packet);
    else return 0;
}

Int spadas::av_interleaved_write_frame(Pointer fmtctx, Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_interleaved_write_frame(fmtctx, packet);
    else return -1;
}

Int spadas::av_write_frame(Pointer fmtctx, Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_write_frame(fmtctx, packet);
    else return -1;
}

Int spadas::av_write_trailer(Pointer fmtctx)
{
    init_ffmpeg_interface();
    if (ffmpegV100.isValid()) return ffmpegV100->av_write_trailer(fmtctx);
    else return -1;
}

void spadas::av_register_all()
{
    init_ffmpeg_interface();
    if (ffmpegV101.isValid()) return ffmpegV101->av_register_all();
}

void spadas::avcodec_register_all()
{
    init_ffmpeg_interface();
    if (ffmpegV101.isValid()) return ffmpegV101->avcodec_register_all();
}

void spadas::av_free_packet(Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV102.isValid()) return ffmpegV102->av_free_packet(packet);
}

Pointer spadas::av_mallocz_av_packet()
{
    init_ffmpeg_interface();
    if (ffmpegV102.isValid()) return ffmpegV102->av_mallocz_av_packet();
    else return NULL;
}

Int spadas::avcodec_encode_video2(Pointer ctx, Pointer packet, Pointer frame, Int *got_packet_ptr)
{
    init_ffmpeg_interface();
    if (ffmpegV102.isValid()) return ffmpegV102->avcodec_encode_video2(ctx, packet, frame, got_packet_ptr);
    else return -1;
}

Pointer spadas::avformat_stream_get_codec(Pointer stream)
{
    init_ffmpeg_interface();
    if (ffmpegV102.isValid()) return ffmpegV102->avformat_stream_get_codec(stream);
    else return NULL;
}

Pointer spadas::avcodec_find_encoder(AVCodecID id)
{
    init_ffmpeg_interface();
    if (ffmpegV102.isValid()) return ffmpegV102->avcodec_find_encoder(id);
    else return NULL;
}

void spadas::avcodec_set_codec_tag(Pointer ctx, UInt tag)
{
    init_ffmpeg_interface();
    if (ffmpegV102.isValid()) ffmpegV102->avcodec_set_codec_tag(ctx, tag);
}

Int spadas::avformat_get_oformat_flags(Pointer fmtctx)
{
    init_ffmpeg_interface();
    if (ffmpegV102.isValid()) return ffmpegV102->avformat_get_oformat_flags(fmtctx);
    else return 0;
}

Int spadas::avcodec_get_flags(Pointer ctx)
{
    init_ffmpeg_interface();
    if (ffmpegV102.isValid()) return ffmpegV102->avcodec_get_flags(ctx);
    else return 0;
}

void spadas::avcodec_set_flags(Pointer ctx, Int flags)
{
    init_ffmpeg_interface();
    if (ffmpegV102.isValid()) return ffmpegV102->avcodec_set_flags(ctx, flags);
}

AVCodecID spadas::avcodec_get_codec_id(Pointer ctx)
{
    init_ffmpeg_interface();
    if (ffmpegV102.isValid()) return ffmpegV102->avcodec_get_codec_id(ctx);
    else return AVCodecID::AV_CODEC_ID_NONE;
}

AVMediaType spadas::avcodec_get_codec_type(Pointer ctx)
{
    init_ffmpeg_interface();
    if (ffmpegV102.isValid()) return ffmpegV102->avcodec_get_codec_type(ctx);
    else return AVMediaType::AVMEDIA_TYPE_NONE;
}

Int spadas::avcodec_decode_video2(Pointer ctx, Pointer frame, Int *got_picture_ptr, Pointer packet)
{
    init_ffmpeg_interface();
    if (ffmpegV102.isValid()) return ffmpegV102->avcodec_decode_video2(ctx, frame, got_picture_ptr, packet);
    else return -1;
}

void spadas::avcodec_set_get_format(Pointer ctx, AVGetFormat get_format)
{
    init_ffmpeg_interface();
    if (ffmpegV103.isValid()) return ffmpegV103->avcodec_set_get_format(ctx, get_format);
}

Int spadas::av_hwframe_transfer_data(Pointer dst, Pointer src, Int flags)
{
    init_ffmpeg_interface();
    if (ffmpegV104.isValid()) return ffmpegV104->av_hwframe_transfer_data(dst, src, flags);
    else return -1;
}

void spadas::avcodec_set_opaque(Pointer ctx, Pointer userData)
{
    init_ffmpeg_interface();
    if (ffmpegV104.isValid()) ffmpegV104->avcodec_set_opaque(ctx, userData);
}

Pointer spadas::avcodec_get_opaque(Pointer ctx)
{
    init_ffmpeg_interface();
    if (ffmpegV104.isValid()) return ffmpegV104->avcodec_get_opaque(ctx);
    else return NULL;
}

Int spadas::av_dict_set(Pointer optionsPtr, const Char *key, const Char *value, Int flags)
{
    init_ffmpeg_interface();
    if (ffmpegV105.isValid()) return ffmpegV105->av_dict_set(optionsPtr, key, value, flags);
    else return -1;
}

//////////////////////////////////////////////////////////////////////////////////////

Int IFFMPEGInterfaceV100::av_get_error_code_value(AVErrorCode code)
{
    return 0;
}

void IFFMPEGInterfaceV100::av_frame_free(Pointer framePtr)
{}

Pointer IFFMPEGInterfaceV100::av_frame_alloc()
{
    return NULL;
}

void IFFMPEGInterfaceV100::av_frame_set_format(Pointer frame, AVPixelFormat format)
{}

void IFFMPEGInterfaceV100::av_frame_set_width(Pointer frame, Int width)
{}

void IFFMPEGInterfaceV100::av_frame_set_height(Pointer frame, Int height)
{}

Int IFFMPEGInterfaceV100::av_frame_get_buffer(Pointer frame, Int align)
{
    return -1;
}

AVPixelFormat IFFMPEGInterfaceV100::av_frame_get_format(Pointer frame)
{
    return AVPixelFormat::AV_PIX_FMT_NONE;
}

Int IFFMPEGInterfaceV100::av_frame_get_width(Pointer frame)
{
    return 0;
}

Int IFFMPEGInterfaceV100::av_frame_get_height(Pointer frame)
{
    return 0;
}

Int* IFFMPEGInterfaceV100::av_frame_get_linesize(Pointer frame)
{
    return NULL;
}

Byte** IFFMPEGInterfaceV100::av_frame_get_data(Pointer frame)
{
    return NULL;
}

Pointer IFFMPEGInterfaceV100::avcodec_find_encoder_by_name(const Char *name)
{
    return NULL;
}

Pointer IFFMPEGInterfaceV100::avcodec_alloc_context3(Pointer codec)
{
    return NULL;
}

void IFFMPEGInterfaceV100::avcodec_set_bit_rate(Pointer ctx, Long bitRate)
{}

void IFFMPEGInterfaceV100::avcodec_set_width(Pointer ctx, Int width)
{}

void IFFMPEGInterfaceV100::avcodec_set_height(Pointer ctx, Int height)
{}

void IFFMPEGInterfaceV100::avcodec_set_time_base(Pointer ctx, Int num, Int den)
{}

void IFFMPEGInterfaceV100::avcodec_set_framerate(Pointer ctx, Int num, Int den)
{}

void IFFMPEGInterfaceV100::avcodec_set_pix_fmt(Pointer ctx, AVPixelFormat format)
{}

void IFFMPEGInterfaceV100::avcodec_set_qmin(Pointer ctx, Int qmin)
{}

void IFFMPEGInterfaceV100::avcodec_set_qmax(Pointer ctx, Int qmax)
{}

Int IFFMPEGInterfaceV100::avcodec_open2(Pointer ctx, Pointer codec, Pointer optionsPtr)
{
    return -1;
}

AVPixelFormat IFFMPEGInterfaceV100::avcodec_get_pix_fmt(Pointer ctx)
{
    return AVPixelFormat::AV_PIX_FMT_NONE;
}

Int IFFMPEGInterfaceV100::avcodec_get_width(Pointer ctx)
{
    return 0;
}

Int IFFMPEGInterfaceV100::avcodec_get_height(Pointer ctx)
{
    return 0;
}

Pointer IFFMPEGInterfaceV100::sws_getContext(Int srcW, Int srcH, AVPixelFormat srcFormat, Int dstW, Int dstH, AVPixelFormat dstFormat, Int flags, Pointer srcFilter, Pointer dstFilter, Pointer param)
{
    return NULL;
}

Pointer IFFMPEGInterfaceV100::av_packet_alloc()
{
    return NULL;
}

void IFFMPEGInterfaceV100::avcodec_free_context(Pointer ctxPtr)
{}

void IFFMPEGInterfaceV100::av_packet_free(Pointer packetPtr)
{}

void IFFMPEGInterfaceV100::sws_freeContext(Pointer ctx)
{}

Int IFFMPEGInterfaceV100::sws_scale(Pointer ctx, Byte **srcSlice, Int *srcStride, Int srcSliceY, Int srcSliceH, Byte **dst, Int *dstStride)
{
    return 0;
}

Int IFFMPEGInterfaceV100::avcodec_send_frame(Pointer ctx, Pointer frame)
{
    return -1;
}

Int IFFMPEGInterfaceV100::avcodec_receive_packet(Pointer ctx, Pointer packet)
{
    return -1;
}

Int IFFMPEGInterfaceV100::av_packet_get_size(Pointer packet)
{
    return 0;
}

Byte* IFFMPEGInterfaceV100::av_packet_get_data(Pointer packet)
{
    return NULL;
}

void IFFMPEGInterfaceV100::av_packet_unref(Pointer packet)
{}

void IFFMPEGInterfaceV100::av_log_set_level(Int level)
{}

Pointer IFFMPEGInterfaceV100::avcodec_find_decoder_by_name(const Char *name)
{
    return NULL;
}

Int IFFMPEGInterfaceV100::av_new_packet(Pointer packet, Int size)
{
    return -1;
}

void IFFMPEGInterfaceV100::av_packet_set_pts(Pointer packet, Long pts)
{}

void IFFMPEGInterfaceV100::av_packet_set_dts(Pointer packet, Long dts)
{}

Int IFFMPEGInterfaceV100::avcodec_send_packet(Pointer ctx, Pointer packet)
{
    return -1;
}

Int IFFMPEGInterfaceV100::avcodec_receive_frame(Pointer ctx, Pointer frame)
{
    return -1;
}

Pointer IFFMPEGInterfaceV100::avcodec_get_hw_config(Pointer codec, Int index)
{
    return NULL;
}

Int IFFMPEGInterfaceV100::avcodec_hw_config_get_methods(Pointer hwConfig)
{
    return 0;
}

AVHWDeviceType IFFMPEGInterfaceV100::avcodec_hw_config_get_device_type(Pointer hwConfig)
{
    return AVHWDeviceType::AV_HWDEVICE_TYPE_NONE;
}

Int IFFMPEGInterfaceV100::av_hwdevice_ctx_create(Pointer hwctxPtr, AVHWDeviceType type, const Char *device, Pointer options, Int flags)
{
    return -1;
}

Pointer IFFMPEGInterfaceV100::av_buffer_ref(Pointer buf)
{
    return NULL;
}

void IFFMPEGInterfaceV100::av_buffer_unref(Pointer bufPtr)
{}

void IFFMPEGInterfaceV100::avcodec_set_hw_device_ctx(Pointer ctx, Pointer hwctx)
{}

Int IFFMPEGInterfaceV100::av_opt_set(Pointer obj, const Char *name, const Char *val, Int search_flags)
{
    return -1;
}

void IFFMPEGInterfaceV100::avcodec_set_gop_size(Pointer ctx, Int gopSize)
{}

void IFFMPEGInterfaceV100::avcodec_set_max_b_frames(Pointer ctx, Int maxBFrames)
{}

Pointer IFFMPEGInterfaceV100::avcodec_get_priv_data(Pointer ctx)
{
    return NULL;
}

void IFFMPEGInterfaceV100::av_frame_set_pts(Pointer frame, Long pts)
{}

Int IFFMPEGInterfaceV100::avformat_open_input(Pointer fmtctxPtr, const Char *url, Pointer fmt, Pointer optionsPtr)
{
    return -1;
}

Int IFFMPEGInterfaceV100::avformat_find_stream_info(Pointer fmtctx, Pointer optionsPtr)
{
    return -1;
}

void IFFMPEGInterfaceV100::avformat_free_context(Pointer fmtctx)
{}

UInt IFFMPEGInterfaceV100::avformat_get_nb_streams(Pointer fmtctx)
{
    return 0;
}

Pointer *IFFMPEGInterfaceV100::avformat_get_streams(Pointer fmtctx)
{
    return NULL;
}

AVMediaType IFFMPEGInterfaceV100::avformat_stream_get_codec_type(Pointer stream)
{
    return AVMediaType::AVMEDIA_TYPE_NONE;
}

AVCodecID IFFMPEGInterfaceV100::avformat_stream_get_codec_id(Pointer stream)
{
    return AVCodecID::AV_CODEC_ID_NONE;
}

Int IFFMPEGInterfaceV100::avformat_stream_get_width(Pointer stream)
{
    return 0;
}

Int IFFMPEGInterfaceV100::avformat_stream_get_height(Pointer stream)
{
    return 0;
}

void IFFMPEGInterfaceV100::avformat_stream_get_time_base(Pointer stream, Int& num, Int& den)
{}

void IFFMPEGInterfaceV100::avformat_close_input(Pointer fmtctxPtr)
{}

Int IFFMPEGInterfaceV100::av_seek_frame(Pointer fmtctx, Int stream_index, Long timestamp, Int flags)
{
    return -1;
}

Int IFFMPEGInterfaceV100::av_read_frame(Pointer fmtctx, Pointer packet)
{
    return -1;
}

Int IFFMPEGInterfaceV100::av_packet_get_stream_index(Pointer packet)
{
    return -1;
}

Long IFFMPEGInterfaceV100::av_packet_get_dts(Pointer packet)
{
    return -1;
}

Int IFFMPEGInterfaceV100::avformat_alloc_output_context2(Pointer fmtctxPtr, Pointer oformat, const Char *format_name, const Char *filename)
{
    return -1;
}

Pointer IFFMPEGInterfaceV100::avformat_new_stream(Pointer fmtctx, Pointer c)
{
    return NULL;
}

void IFFMPEGInterfaceV100::avformat_stream_set_codec_type(Pointer stream, AVMediaType type)
{}

void IFFMPEGInterfaceV100::avformat_stream_set_codec_id(Pointer stream, AVCodecID id)
{}

void IFFMPEGInterfaceV100::avformat_stream_set_bit_rate(Pointer stream, Long rate)
{}

void IFFMPEGInterfaceV100::avformat_stream_set_width(Pointer stream, Int width)
{}

void IFFMPEGInterfaceV100::avformat_stream_set_height(Pointer stream, Int height)
{}

void IFFMPEGInterfaceV100::avformat_stream_set_time_base(Pointer stream, Int num, Int den)
{}

void IFFMPEGInterfaceV100::avformat_stream_set_codec_tag(Pointer stream, UInt tag)
{}

Pointer IFFMPEGInterfaceV100::avformat_get_pb_ptr(Pointer fmtctx)
{
    return NULL;
}

Int IFFMPEGInterfaceV100::avio_open(Pointer ioctxPtr, const Char *url, Int flags)
{
    return -1;
}

Int IFFMPEGInterfaceV100::avformat_write_header(Pointer fmtctx, Pointer optionsPtr)
{
    return -1;
}

Pointer IFFMPEGInterfaceV100::avformat_get_pb(Pointer fmtctx)
{
    return NULL;
}

Int IFFMPEGInterfaceV100::avio_close(Pointer ioctx)
{
    return -1;
}

void IFFMPEGInterfaceV100::av_init_packet(Pointer packet)
{}

void IFFMPEGInterfaceV100::av_packet_set_size(Pointer packet, Int size)
{}

void IFFMPEGInterfaceV100::av_packet_set_data(Pointer packet, Byte *data)
{}

void IFFMPEGInterfaceV100::av_packet_set_flags(Pointer packet, Int flags)
{}

Int IFFMPEGInterfaceV100::av_packet_get_flags(Pointer packet)
{
    return 0;
}

Int IFFMPEGInterfaceV100::av_interleaved_write_frame(Pointer fmtctx, Pointer packet)
{
    return -1;
}

Int IFFMPEGInterfaceV100::av_write_frame(Pointer fmtctx, Pointer packet)
{
    return -1;
}

Int IFFMPEGInterfaceV100::av_write_trailer(Pointer fmtctx)
{
    return -1;
}

void IFFMPEGInterfaceV101::av_register_all()
{}

void IFFMPEGInterfaceV101::avcodec_register_all()
{}

void IFFMPEGInterfaceV102::av_free_packet(Pointer packet)
{}

Pointer IFFMPEGInterfaceV102::av_mallocz_av_packet()
{
    return NULL;
}

Int IFFMPEGInterfaceV102::avcodec_encode_video2(Pointer ctx, Pointer packet, Pointer frame, Int *got_packet_ptr)
{
    return -1;
}

Pointer IFFMPEGInterfaceV102::avformat_stream_get_codec(Pointer stream)
{
    return NULL;
}

Pointer IFFMPEGInterfaceV102::avcodec_find_encoder(AVCodecID id)
{
    return NULL;
}

void IFFMPEGInterfaceV102::avcodec_set_codec_tag(Pointer ctx, UInt tag)
{}

Int IFFMPEGInterfaceV102::avformat_get_oformat_flags(Pointer fmtctx)
{
    return 0;
}

Int IFFMPEGInterfaceV102::avcodec_get_flags(Pointer ctx)
{
    return 0;
}

void IFFMPEGInterfaceV102::avcodec_set_flags(Pointer ctx, Int flags)
{}

AVCodecID IFFMPEGInterfaceV102::avcodec_get_codec_id(Pointer ctx)
{
    return AVCodecID::AV_CODEC_ID_NONE;
}

AVMediaType IFFMPEGInterfaceV102::avcodec_get_codec_type(Pointer ctx)
{
    return AVMediaType::AVMEDIA_TYPE_NONE;
}

Int IFFMPEGInterfaceV102::avcodec_decode_video2(Pointer ctx, Pointer frame, Int *got_picture_ptr, Pointer packet)
{
    return -1;
}

void IFFMPEGInterfaceV103::avcodec_set_get_format(Pointer ctx, AVGetFormat get_format)
{}

Int IFFMPEGInterfaceV104::av_hwframe_transfer_data(Pointer dst, Pointer src, Int flags)
{
    return -1;
}

void IFFMPEGInterfaceV104::avcodec_set_opaque(Pointer ctx, Pointer userData)
{}

Pointer IFFMPEGInterfaceV104::avcodec_get_opaque(Pointer ctx)
{
    return NULL;
}

Int IFFMPEGInterfaceV105::av_dict_set(Pointer optionsPtr, const Char *key, const Char *value, Int flags)
{
    return -1;
}